/**
 * (C) 2007 - Boston University
 * 	Contact: Michael Ocean 

  This work is a part of the larger snBench project.  Information
  can be found at: http://csr.bu.edu/snbench

  This program is free software; you can redistribute it and/or
  modify it under the terms of Version 2 of the GNU General Public 
  License.
  
  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  General Public License for more details.
  
  You should have received a copy of the GNU Library General Public
  License along with this library; if not, write to the Free
  Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
**/
package snbenchlib;

import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

/**
 * 	MJO: modified version of a cs511 contribution.  These should be accessed via the
 * "Log" object in snbenchlib 
 * 
 *     OVERVIEW:  
 *     This is a mutable object that provides methods to
 *     gather performance/trace data using user-defined format.
 *     
 *     Calls to this object are synchronized around the Trace.class
 *     so this may be a performance/concurrency bottleneck and 
 *     should be disabled once confident.
 */

public class Trace {

	/**
	 * Trace levels are 
	public static enum TraceLevels {OFF(0), MINIMAL(3),DEBUG(5),HELP(7), VERBOSE(10);
		private final int level;
	    TraceLevels(int _level) {
	        this.level = _level;
	    }
	    public int level(){return this.level;}
	};
	*/
	
	public static int VERBOSE = 10;  /* the most information you can get */
	public static int HELP = 7;		 /* more information than DEBUG, yet less than VERBOSE */
	public static int DEBUG = 5;	 /* moderate debugging info */
	public static int MINIMAL = 3;	 /* the bear minimum information you can get */
	public static int OFF = 0;		 /* "none" -- notice calls to .log(msg) without tracelevel still produce output! */
	
   //OVERVIEW:  This is a mutable object that provides methods to
   //           gather performance data using user-defined format.
   //           A typical format is {scheduler, wrrscheduler, outputfile}.
   //           The Trace module will then restrict characterization for
   //           scheduler and wrrscheduler classes and writes gathered 
   //           time data to "outputfile"
   //     
   //
   // AF(c):  {c.tracelevel, c.dt_sec, c.os, c.dt_milli, c.trace, c.tracefile,c.dt_nano,c.filename}
   // RI:     if c.trace == true, then do trace
   //         
	private int tracelevel = DEBUG;
	private boolean trace = true;
	private long dt_milli, dt_nano;
	private Date dt_sec;
	
	private MyWriterWrapper os;
	private String prefix;
	
	private class MyWriterWrapper{
		PrintStream ps = null;
		sxe.Gui gui = null;
		
		MyWriterWrapper(PrintStream _stream){
			ps = _stream;
		}
		
		MyWriterWrapper(sxe.Gui _gui){
			gui = _gui;
		}
		
		public void println(String str){
			if(gui!=null)
				gui.appendText(str);
			else
				ps.println(str);
		}

		public void printStackTrace(Exception e) {
			if(ps!=null){
				e.printStackTrace(ps);
			}
			else{
				StringWriter sw =  new StringWriter(); 
				e.printStackTrace(new PrintWriter(sw));
				gui.appendText(sw.getBuffer().toString());
			}
			
		}

		public void close() {
			if(ps!=null)
				ps.close();
		}
	};
    /**
     * added by MJO: Preferred constructor.  Creates a file for tracing to.
     * 	prefix is optional.  You might not want to use one if you're writing
     *  everything to different files. 
     * 
     * 
     * @param sPrefix - a string to pre-pend to every line
     * @param sOutputFileName - a file name to be created | "out" for stdout
     * @param iTraceLevel - the default tracelevel.  Only items below this
     * 		number will be shown.  Set to 0 to disable.
     */
    public Trace(String sPrefix, String sOutputFileName, int iTraceLevel)
    {    
		try {
			if (sOutputFileName.equalsIgnoreCase("out"))
				os  = new MyWriterWrapper(System.out);
			else
				os  = new MyWriterWrapper(new PrintStream(new FileOutputStream(sOutputFileName)));
		} catch (FileNotFoundException e){
			System.out.println(prefix + "Trace file: " + sOutputFileName + " not found/could not be created.");
			os  = new MyWriterWrapper(System.out);
		}
		init(sPrefix,iTraceLevel);
    }
    
    public Trace(String sPrefix, sxe.Gui gui, int iTraceLevel)
    {    
		os  = new MyWriterWrapper(gui);
		init(sPrefix,iTraceLevel);
    }
    
    private void init(String sPrefix, int iTraceLevel){
    	prefix = sPrefix;
    	if(iTraceLevel  > 0){	
    		tracelevel = iTraceLevel;
    	}
    	else{
    		trace=false;
    	}
    }
    
    /**
     * if (tl <= current_tracelevel) then a line is
     * 	written indicating the current time in seconds
     *  and the class,method, and line number from which
     *  you called this method.  
     * 
     * @param tl - maximum tracelevel at which you
     * 		want this message to appear 
     */
    public void applyTimeStamp(int tl) {
        // EFFECTS: Writes one line of output of the format to the 
        //          tracefile
    	if(tl > tracelevel)
    		return;
    	
    	StackTraceElement ste = new Exception().getStackTrace()[1]; 
        int pos = ste.getLineNumber();
        String cls = ste.getClassName();
        String fn = ste.getMethodName();
    	
    	if(!trace)
    		return;
    	
    	final String sdt = new SimpleDateFormat().format(new Date());
    	String buf = prefix + cls + "." + fn + "():line " + pos + " @ " + sdt; 

        synchronized (Trace.class) {
            os.println(buf);
        }
 
    }
    
    /**
     * Prints the given string to the output trace file <emph>regardless
     * of trace being enabled or tracelevel<emph>.  
     * 
     * Use it for essential output.
     * 
     * @param log - the string to be recorded
     */
    public void log(String log) {
    	//EFFECTS:  print the input log message if trace is enabled
    	      	
        synchronized (Trace.class) {
            os.println(prefix+log);
        }

    }
    
    /**
     * prints the input string only if trace is enabled and 
     * tracelevel used in the method is lower than that configured
     * in properties file.
     * 
     * @param log - the string to be recorded
     * @param tl - the maximum tracelevel at which this string 
     * 			should appear; e.g., Trace.VERBOSE only shows up
     * 			when running with "maximum" trace messages set, while
     * 			Trace.MINIMAL means it will show up in all trace 
     * 			modes from Trace.MINIMAL up to and include Trace.VERBOSE
     */
    public void log(String log, int tl) {
    	
    // EFFECTS:  print the input log message if trace is enabled and 
    	//       tracelevel used for logging is lower than the configured
    	//       value.
    	
    	if ((trace == false) || (tl > tracelevel))
    		return;
        synchronized (Trace.class) {
            os.println(prefix+log);
        }
 
    }
    
    /**
     * Log an exception to the trace file
     * 
     * @param e
     */
    public void printStackTrace(Exception e){
    	synchronized (Trace.class) {
    		os.printStackTrace(e);
    	}
    }
 
    /**
     * prints the stacktrace only if trace is enabled and 
     * tracelevel used in the method is lower than that configured
     * in properties file.
     * 
     * @param e - the exception
     * @param tl - the maximum tracelevel at which this string 
     * 			should appear; e.g., Trace.VERBOSE only shows up
     * 			when running with "maximum" trace messages set, while
     * 			Trace.MINIMAL means it will show up in all trace 
     * 			modes from Trace.MINIMAL up to and include Trace.VERBOSE
     */
    public void printStackTrace(Exception e, int tl) {
    	        	
        	if ((trace == false) || (tl > tracelevel))
        		return;
            synchronized (Trace.class) {
            	os.printStackTrace(e);
            }
     
        }
     
    /** MJO: ITEMS BELOW THIS LINE I CAN'T SPEAK FOR ... **/
	/**
	 * Default constructor that creates all configuration from the 
	 * default scheduler.properties file
	 *
    public Trace() {
        // EFFECTS: Initializes the format to default format 
        //          {scheduler, wrrschedule, tracefile} 
    	
     	System.out.println("In class trace");
    	
	    Properties props = openPropsFile();
	    if(props!=null)
	    	setVar(props);
	    
	 	createOutFile(filename);
    }
    */
    
    /**
     * updates trace and uses second level granularity to log
     * current time of system.
     * @param cls
     * @param fn
     * @param pos
     */
    private void updateTrace(String cls, String fn, char pos) {

        // EFFECTS: Writes one line of output of the format to the 
        //          tracefile 
    	if(!trace)
    		return;
    	
    	final String sdt = new SimpleDateFormat().format(new Date());
    	String buf = prefix + cls + " " + fn + " " + sdt + " " + pos; 

        synchronized (Trace.class) {
            os.println(buf);
        }
 
    }
    
     /**
     * records time of call of this method at Microseconds level
     * granularity
     *
     */
    public void recordTimeMicro() {

        // EFFECTS: records current ticks to the precision of Micro
        //          seconds
    	if(!trace)
    		return;     

    }

    /**
     * recordsTime at the granularity level of seconds
     * @param cls
     * @param fn
     * @param pos
     */
    public void recordTime(String cls, String fn, char pos) {

        // EFFECTS: records current ticks to the precision of 
        //          seconds
       	// MODIFIES: this.dt_sec to current system time in seconds
        
    	if(!trace)
    		return;
    	
    	Calendar cl = Calendar.getInstance();
		dt_sec = cl.getTime();
		updateTrace(cls, fn,pos);

    }

    /**
     * records time at the granularity level of milliseconds.
     * @param cls
     * @param fn
     * @param pos
     */
    public void recordTimeMilli(String cls, String fn, char pos) {

        // EFFECTS: records current ticks to the precision of Milli
        //          seconds
    	// MODIFIES: this.dt_milli to current system time in milliseconds
     
    	if(!trace)
    		return;
     	dt_milli = System.currentTimeMillis();
		updateTrace(cls,fn,pos);

    }
    
    /**
     * records time at Nanosecond granularity
     * @param cls
     * @param fn
     * @param pos
     */
    public void recordTimeNano(String cls, String fn, char pos) {

        // EFFECTS: records current ticks to the precision of Milli
        //          seconds
    	// MODIFIES: this.dt_milli to current system time in milliseconds
     
    	if (!trace)
    		return;
     	dt_nano = System.nanoTime();
		updateTrace(cls,fn,pos);

    }
    

    /**
     * closes the output file handle and performs all other cleanup.
     *
     */
    public void terminate()
    {
    	//EFFECTS: cleanup routine to close output filename
    	os.close();
    }
}
